<?php defined('SYSPATH') or die('No direct script access.');

/**
 * Authoreyes Class
 * 
 * Security handler that provides functionality to handle login, logout,
 * registration, and reset password requests.
 * 
 * It can also verify the logged in status of 3 user classes
 *
 * => superadmin  - Has permissions to everything and can also create other admin)
 * => admin       - You can choose what to let him manage (as superadmin))
 * => user        - A registered user, and you can decide to give in rights
 *                  to access some specific areas (controllers) of your application
 *
 * The class requires the use of
 *
 * => Libraries => Kohana: Database, Encrypt, Session, Input - AuthorEyes: AE_Validation, Front
 * => Helpers   => Kohana: url, form - AuthorEyes: authoreyes
 *
 * Passwords are encrypted with the algorithm used in the method hash_password($password)
 * specified by $config['ae_user_pass_settings']['hash_method'].  This algorithm can be 
 * changed at any time.
 *
 * -----------------------------------------------------------------------------
 * Copyright (C) 2008   Patrick Clark (pubblichi.com/blog)
 * -----------------------------------------------------------------------------
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * ------------------------------------------------------------------------------
 * @package     AuthorEyes
 * @subpackage  Libraries
 * @category    Authentication
 * @author      Patrick Clark (Tap)
 * @copyright   Copyright (c) 2008, pubblichi.com
 * @license     http://www.gnu.org/licenses/lgpl.html
 * @link        http://www.pubblichi.com/authoreyes
 * @info        This is an adaptation of the FreakAuth Light authentication library,
 * @info        written specifically for Kohana.
 * @info     	FreakAuth Light written by Daniel Vecchiato (danfreak) & Christophe Gragnic (grahack)
 * @info        Visit www.4webby.com/freakauth to download FreakAuth Light for CodeIgniter
 * @version     0.1
 *
 */
class Authoreyes_Core {
	/*-------------+
	|  PROPERTIES  |
	+-------------*/
	// Kohana library instances.
	protected $session;
	protected $db;
	protected $encrypt;
	protected $input;
	
	// Model instances.
	protected $user_mdl;
	protected $user_temp;
	protected $user_profile;
	protected $user_token;
	
	/*----------+
	|  METHODS  |
	+----------*/
	/**
	 * Factory method: creates a new instance of AuthorEyes.
	 * 
	 * @return object
	 */
	public static function factory()
	{
		return new Authoreyes();
	}
	
	/**
	 * Initialize the AuthorEyes class (library).
	 * 
	 * This is where we will create an instance of the database library, as well
	 * as the use of user_mdl, user_temp and user_token.
	 *  - models/user.php
	 *  - models/user_temp.php
	 *  - models/user_token.php (For autologin)
	 *  
	 * Also checks to see if we should use a custom user profile, and loads the
	 * user_profile model.
	 *  - models/user_profile.php
	 * 
	 * The use of helpers here is inherent in Kohana.
	 * 
	 * @requires libraries  (database, encrypt, session, input)
	 * @requires helpers    (form, url, authoreyes)
	 * @requires models     (user_mdl, user_temp, [user_profile])
	 * @return void
	 */
	public function __construct()
	{
		// Call initialization function.
		$this->_init();
		
		// Create new instances of the Kohana libraries for use within this library.
		$this->db = new Database;
		$this->encrypt = new Encrypt;
		$this->session = new Session;
		$this->input = new Input;
		
		// Create new instances of our models. (user, user_temp)
		$this->user_mdl = new User_Model;
		$this->user_temp = new User_Temp_Model;
		
		// See if we should use a custom user profile (Create new instance of user_profile model).
		if (Config::item('authoreyes.ae_create_user_profile'))
		{
			$this->user_profile = new User_Profile_Model;
		}
		
		// For debugging purposes - after all our other entities are initialized.
		Log::add('debug', 'AuthorEyes Library loaded');
	}
	
	
	/**
	 * Initializes the security settings.  Turns 'website off' if $config['ae'] is set to FALSE.
	 * 
	 * @return void
	 */
	private function _init()
	{
		// Checks to see if the AuthorEyes system is turned off.
		if (!Config::item('authoreyes.ae'))
		{			
			// Instantiate turned_off view and render the output.
			$system_off_view = new View(Config::item('authoreyes.ae_system_turned_off'));
			
			// Preparing message.
			$system_off_view->website_name = Config::item('authoreyes.ae_website_name');
			$system_off_view->message = sprintf(Kohana::lang('authoreyes.ae_website.turned_off_message'), $system_off_view->website_name);
			
			// Render turned_off view template - this will be fully customizable.
			$system_off_view->render(TRUE);
			
			// Exit processing.
			exit();
		}
	}
	
	/**
	 * Method used to restrict access to controllers or methods of controllers
	 * to the specified category of users.
	 * 
	 * It requires 2 optional parameters:
	 *  - The first parameter specifies the user group (i.e. 'admin').
	 *  - The second parameter specifies whether the area is reserved ONLY to
	 *    that group (TRUE) or if it is accessible by groups higher in the 
	 *    heirarchy.
	 *    
	 * Example usage in a controller:
	 *  1.  $this->authoreyes->check();
	 *      This restricts access to registered users and usergroups higher in
	 *      the heirarchy (i.e. admin, superadmin).
	 *      
	 *  2.  $this->authoreyes->check('admin');
	 *      This restricts access to 'admin' users and users who belong to roles
	 *      higher in the heirarchy (i.e. superadmin).
	 *      
	 *  3.  $this->authoreyes->check('admin', TRUE);
	 *      This restricts access to 'admin' users ONLY.
	 *      
	 * @param string   The role to whom the area is restricted to ($lock_to_role).
	 * @param boolean  Check whether the restriction is locked to one group ($only).
	 */
	public function check($lock_to_role = NULL, $only = FALSE)
	{
		// Check to see if role exists
		$this->_role_exists($lock_to_role);
		
		// Check who did the request and build role heirarchy.
		$who_is = $this->session->get('role');
		
		// Check to see if we have a role stored in the session for this user.
		// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		// Possibly check to see where session data is being stored?
		if (Config::item('authoreyes.ae') AND !empty($who_is))
		{
			// Gets the locked role hierarchy value.
			$heirarchy = Config::item('authoreyes.ae_roles');
			
			// If we didn't specify to who we will reserve the action,
			// let's restrict it to registered users.
			if ($lock_to_role == NULL)
			{
				$lock_to_role = 'user';
			}
			
			// Let's see who we reserved the area for (what level access did they lock to?).
			$lock_heirarchy = $heirarchy[$lock_to_role];
			// Let's see who requested to access this area (what level access do they have?).
			$request_heirarchy = $heirarchy[$who_is];
			
			// Let's see if we decided to restrict access ONLY to a given category.
			switch ($only)
			{
				case TRUE:
					$request_heirarchy == $lock_heirarchy ? $condition = TRUE : $condition = FALSE;
					break;
				// FALSE or not specified.
				default:
					$request_heirarchy <= $lock_heirarchy ? $condition = TRUE : $condition = FALSE;
					break;
			}
			
			// If the user who made the request doesn't have enough credentials, deny them access.
			if ($condition == FALSE)
			{
				$this->deny_access($who_is);
			}
		}
		// If there is no session data stored, the user is not registered or logged in.
		else
		{
			// Deny the user access to part of the controller containing this check.
			$this->deny_access($who_is);
		}
	}
	
	/**
	 * Handles the case where the one who did the request doesn't have enough credentials.
	 * 
	 * If ae_deny_with_flash_message = TRUE in the config file, displays a flash message
	 * and redirects to the referer page (or homepage if there is no referer page).
	 * 
	 * Else displays the ae_denied_page (see authoreyes.php config file).
	 * 
	 * ---------------------------------------------------
	 * EXAMPLE USAGE (in a controller)
	 * ---------------------------------------------------
	 * $this->authoreyes->deny_access('user')
	 * 
	 * @param string  The role of the user we are denying access ($role).
	 */
	public function deny_access($role)
	{
		// Check to see if role exists
		$this->_role_exists($role);
		
		// Use the fancy flash message
		if (Config::item('authoreyes.ae_deny_with_flash_message'))
		{
			// Checks if visitor is a GUEST.
			if ($role == '')
			{
				// First, we have to store the requested page in order to 
				// serve it back to the visitor after a successful login.
				$this->session->set_flash('requested_page', Router::$current_uri);
				
				// Then we redirect to the login form with an 'access denied' message.
				// Maybe if the visitor can log in, he'll get some more permissions...
				$msg = Kohana::lang('authoreyes.ae_website.no_credentials_guest_message');
				// !!!!!REMEMBER TO UNCOMMENT THIS!!!!! authoreyes_help::flash_message($msg);
				url::redirect(Config::item('authoreyes.ae_controller_conventions.login_uri'), 301);
			}
			// Checks if visitor is a not a GUEST (user, admin, superadmin, etc..)
			else
			{
				// YOU GET THE MESSAGE?
				$msg = Kohana::lang('authoreyes.no_credentials_user_message');
				// !!!!!REMEMBER TO UNCOMMENT THIS!!!!! authoreyes_help::flash_message($msg);
				
				// Checks if visitor came to the site with an http_referer.
				if (isset($_SERVER['HTTP_REFERER']))
				{
					$referer = $_SERVER['HTTP_REFERER'];

					// Checks if http_referer is from an external site.
					if (preg_match("|^".url::base()."|", $referer) == 0)
					{
						// Users are taken to the page defined in the config file.
						url::redirect(Config::item('authoreyes.ae_denied_from_ext_location'));
					}
					// Checks if visitor came from our site.
					else
					{
						// Arrived from 'redirect to requested page'?
						$this->session->keep_flash('requested_page');
						header("location:".$_SERVER['HTTP_REFERER']);
						exit();
					}
				}
				// If visitor did not come to the site with an http_referer, redirect to the page
				// defined in the config file too.
				else
				{
					url::redirect(Config::item('authoreyes.ae_denied_from_ext_location'), 301);
				}
			}
		}
		else
		{
			$page = Config::item('authoreyes.ae_denied_page');
			$data['role'] = $role;
			// This is how we stop the execution.
			$denied_page = new View($page, $data);
			$denied_page->render(TRUE);
			exit();
		}
	}
	
	/**
	 * Checks to see if a user is an administrator.  Uses session library.
	 * 
	 * Returns FALSE if AuthorEyes system is not activated.
	 * Returns TRUE if admin or superadmin, otherwise FALSE.
	 * 
	 * @return  bool
	 */
	public function is_admin()
	{
		// Ensure that AuthorEyes is turned on.
		if ($this->session AND Config::item('authoreyes.ae'))
		{
			// Fetch username and role.
			$username = $this->session->get('username');
			$role = $this->session->get('role');
			
			// Check to see if user is admin or superadmin.
			if ($username != FALSE AND $role != FALSE AND ($role == 'admin' OR $role == 'superadmin'))
			{
				return TRUE;
			}
		}
		// If username not activated or doesn't exist, or AuthorEyes is turned off.
		return FALSE;
	}
	
	/**
	 * Checks to see if user is a super administrator.  Uses session library.
	 * 
	 * Returns FALSE if AuthorEyes system is not activated.
	 * Returns TRUE if superadmin, otherwise FALSE.
	 * 
	 * @return  bool
	 */
	function is_super_admin()
	{
		// Ensure that AuthorEyes is turned on.
		if ($this->session AND Config::item('authoreyes.ae'))
		{
			// Fetch username and role.
			$username = $this->session->get('username');
			$role = $this->session->get('role');
			
			// Check to see if user is superadmin.
			if ($username != FALSE AND $role != FALSE AND $role == 'superadmin')
			{
				return TRUE;
			}
		}
		// If username not activated or doesn't exist, or AuthorEyes is turned off.
		return FALSE;
	}
	
	/**
	 * Checks to see if a user is logged in.
	 * Returns FALSE if AuthorEyes system is not activated.
	 * Returns TRUE if a valid user is logged in, otherwise FALSE.
	 * 
	 * @return  bool
	 */
	function is_valid_user()
	{
		// Ensure that AuthorEyes is turned on.
		if ($this->session AND Config::item('authoreyes.ae'))
		{
			if ($this->get_username() != '')
			{
				return TRUE;
			}
		}
		// If username is not activated or doesn't exist, or AuthorEyes is turned off.
		return FALSE;
	}
	
	/**
	 * Method used to check if a logged in user belongs to the customer role (group) specified
	 * in the first parameter.
	 * 
	 * It requires 2 optional parameters:
	 *  1.  The first specifies the user roles as a comma separated string or an array.
	 *  2.  The second specifies whether we want to check to the specified roles
	 *      ONLY or for AT LEAST those group membership in the heirarchy.
	 *      (Returns TRUE also if the logged user belongs to a group higher in
	 *      the heirarchy.)
	 * 
	 * Example usage in a controller:
	 * (See the relative helper belongs_to_group() to use it in views.)
	 *  1.  $this->authoreyes->belongs_to_group();
	 *      Returns TRUE if the visitor is logged in and is AT LEAST an user.
	 *  2.  $this->authoreyes->belongs_to_group('user, editor');
	 *      Returns TRUE if the visitor is logged in and is AT LEAST an user
	 *      or an editor (therefore it returns TRUE also if he belongs to usergroups
	 *      higher in the heirarchy (i.e. admin, superadmin)
	 *  3.  $this->authoreyes->belongs_to_group('admin', TRUE);
	 *      Returns TRUE if the visitor is logged in and is an 'admin' ONLY.
	 * 
	 * @param   string    Comma separated user roles ($group).
	 * @param   bool      Whether to lock to specified roles ($only).
	 * @return  bool
	 */
	public function belongs_to_group($group = NULL, $only = FALSE)
	{
		$this->_role_exists($group);
		
		// Ensure that AuthorEyes is turned on.
		if($this->session AND Config::item('authoreyes.ae'))
		{
			// Fetch username and role.
			$username = $this->session->get('username');
			$who_is = $this->session->get('role');
			
			// Check to see if username and role are stored in session.
			if ($username != FALSE AND $who_is != FALSE)
			{
				// If we didn't specify who we are looking for let's look if the request
				// comes from an 'user'.
				if ($group == NULL)
				{
					$group = 'user';
				}
				
				if (!is_array($group))
				{
					// Turn our group string into an array.
					$groups = explode(",", $group);
				}
				else
				{
					// $group is already an array so just
					// assign it to $groups
					$groups = $group;
				}
				
				// Reassign $group to an array.
				$group = array();
				
				// Eliminate the possible whitespaces at the beginning and end of groups
				// names passed as parameters to this function.
				foreach ($groups as $grp)
				{
					$group[] = trim($grp);
				}
				
				// Let's see if we decided to check if it belongs ONLY to a given group.
				switch ($only)
				{
					// We decided to check if it belongs ONLY to a given group.
					case TRUE:
						in_array($who_is, $group) ? $condition = TRUE : $condition = FALSE;
						break;
					
					// We decided to check if it belongs to AT LEAST a given group.
					default:
						// Gets the locked role heirarchy value.
						$heirarchy = Config::item('authoreyes.ae_roles');
						
						// Let's see who we are looking for.
						foreach ($group as $value)
						{
							$group_heirarchy[] = $heirarchy[$value];
						}
						
						// We want to get the highest role assignment number passed through in group.
						$group_heirarchy = max($group_heirarchy);
						
						// Let's see who accessed.  We need to get the role-heirarchy value
						// of the visitor who did the request.
						$who_heirarchy = $heirarchy[$who_is];
						
						// If the visitor that made the request has a lower value than the specified
						// value, return TRUE. (Makes sense because the lower the value, the more
						// access is granted!)
						$who_heirarchy <= $group_heirarchy ? $condition = TRUE : $condition = FALSE;
						break;
				}
				
				// If who did the request doesn't have enough credentials.
				if ($condition == TRUE)
				{
					return TRUE;
				}
			}
		}
		// If condition == FALSE, session isn't initialized, or user wasn't found (not logged in).
		return FALSE;
	}
	
	/**
	 * Performs the login procedure for both user login and form administrators login.
	 * 
	 * @return void
	 */
	public function login()
	{
		if (!Config::item('authoreyes.ae'))
		{
			url::redirect(Config::item('authoreyes.ae_login_success_action'), 301);
		}
		
		if ($this->session)
		{
			// Fetch login form information. REMEMBER TO GET AUTOLOGIN INFORMATION AS WELL.
			$values = $this->get_login_form();
			
			// Make sure visitor entered values in the form fields.
			$username = (isset($values['username']) ? $values['username'] : FALSE);
			$password = (isset($values['password']) ? $values['password'] : FALSE);
			$remember = (isset($values['remember']) ? $values['remember'] : FALSE);
			
			// Checks if the user entered information into the fields.
			if ($username != FALSE AND $password != FALSE)
			{
				// Encode the password.
				$password = $this->hash_password($password);
				
				// Use the input username and password check against 'user' table to 
				// check if user is banned.
				$query = $this->user_mdl->get_user_for_login($username, $password);
				
				// Make sure the visitor has a user.
				if ($query->count() == 1)
				{
					// Populate the database stuff into an array.
					$row = $query->result();
					$fields = array('id', 'username', 'country_id', 'email',
					                'role', 'last_visit', 'created', 'modified');
					
					foreach ($fields as $field)
					{
						$userdata[$field] = $row->$field;
					}
					
					// Verfies if a user has not been banned from the site.
					// (i.e. user table, banned = 1)
					if ($row->banned == 0)
					{
						$this->_set_logindata($userdata);
						
						// Set FLASH MESSAGE
						// (Redirection is done in Front if TRUE is returned)
						authoreyes_help::flash_message(Kohana::lang('authoreyes.ae_activation.login_message'));
						return TRUE;
					}
				}
			}
		}
		
		// On error send user back to login page, and add error message.
		// Set FLASH MESSAGE.
		$message = Kohana::lang('authoreyes.ae_activation.invalid_user_message');
		authoreyes_help::flash_message($message);
		// CHECK FIX ME: url::redirect(Config::item('???'))
		return FALSE;
	}
	
	/**
	 * Performs the logout procedure.
	 * 
	 * @return void
	 */
	public function logout()
	{
		// Checks if a session exists.
		if ($this->session)
		{
			// Fetch username from session data.
			$username = $this->session->get('username');
			
			if($username != FALSE)
			{
				// Deletes the userdata stored in the session for the user that logged out.
				$this->_unset_user($username);
			}
		}
		// Set FLASH MESSAGE.
		$msg = Kohana::lang('authoreyes.ae_activation.logout_message');
		authoreyes_help::flash_message($msg);

		url::redirect(Config::item('authoreyes.ae_logout_success_action'), 301);
	}
	
	/**
	 * Performs the registration procedure.
	 * 
	 * Returns TRUE if the registration is successful, FALSE if unsuccessful.
	 * 
	 * @return  bool
	 */
	public function register()
	{
		// Let's clean the user_temp table if we use registration with e-mail verification.
		if (!Config::item('authoreyes.ae_register_direct'))
		{
			$this->clean_expired_user_temp();
		}
		
		// Let's check if the system is turned on and if we allow users to register.
		if (!Config::item('authoreyes.ae') OR !Config::item('authoreyes.ae_allow_user_registration'))
		{
			return FALSE;
		}
		
		// Check if session exists.
		if ($this->session)
		{
			// Fetch our post values and assign the value from the form (or FALSE).
			$values = $this->get_registration_form();
			$username = (isset($values['username']) ? $values['username'] : FALSE);
			$password = (isset($values['password']) ? $values['password'] : FALSE);
			$email = (isset($values['email']) ? $values['email'] : FALSE);
			
			// Ensure that all fields were filled in to enter this block.
			if ($username != FALSE AND $password != FALSE AND $email != FALSE)
			{
				$password_email = $password;
				$password = $this->hash_password($password);
				
				// Reassignment to the encoded password.
				$values['password'] = $password;
				
				// Checks if we go for standard activation with e-mail verification.
				// (i.e. $config['ae_register_direct] = FALSE)
				if (!Config::item('authoreyes.ae_register_direct'))
				{
					// Generates the activation code.
					$activation_code = $this->_generate_random_string();
					$values['activation_code'] = $activation_code;
					// Inserts temporary user information.
					$this->user_temp->insert_user_for_registration($values);
					
					// Use the input username and password and check against 'user_temp' table.
					// Needed to find the user_temp ID for the activation link.
					$query = $this->user_temp->get_user_login_data($username, $password);
					
					// Initialize the user ID.
					$user_id = 0;
					
					// Make sure we returned a row.
					if ($query != NULL AND $query->count() > 0)
					{
						$row = $query->result();
						$user_id = $row->id;
						
						$this->_send_activation_email($user_id, $username, $password_email, $email, $activation_code);
						
						return TRUE;
					}
				}
				// We're using direct registration in this case.
				// (i.e. $config['ae_register_direct']) = FALSE)
				else
				{
					// Let's insert the values in the user table.
					$query = $this->user_mdl->insert_user($values);
					
					// If affected rows == 1, set a flash message and redirect to login.
					if ($query->count() == 1)
					{
						// Checks if there is a custom user profile.
						if (Config::item('authoreyes.ae_create_user_profile'))
						{
							// Get's the last insert ID.
							$data_profile['id'] = $query->insert_id();
							// And insert the data profile.
							$this->user_profile->insert_user_profile($data_profile);
						}
						// Flash the user with a nice little message.
						authoreyes_help::flash_message(Kohana::lang('authoreyes.ae_activation.activation_success_message'));
						return TRUE;
					}
				}
			}
			// Checks if username or password or email was NOT entered.
			else
			{
				// Set FLASH MESSAGE.
				authoreyes_help::flash_message(Kohana::lang('authoreyes.ae_registration.invalid_register_message'));
				// CHECK FIX ME: url::redirect(Config::item('???'))
				return FALSE;
			}
		}
	}
	
	/**
	 * Handles the user activation requests.
	 * 
	 * @param  int      User ID ($id).
	 * @param  varchar  User activation code ($activation_code).
	 * @return bool
	 */
	public function activation($id, $activation_code)
	{
		// Let's clean the user_temp table if we use registration with e-mail verification.
		if (!Config::item('authoreyes.ae_register_direct'))
		{
			$this->clean_expired_user_temp();
		}
		
		// Ensures that we passed in some valid values.
		if ($id > 0 AND $activation_code != '')
		{
			// Fetches userdata from user_temp table.
			$query = $this->user_temp->get_user_for_activation($id, $activation_code);
			
			// Deletes the record from the user_temp table.
			$this->user_temp->delete_user_after_activation($id);
			
			// Makes sure the delete was successful.
			if ($query->count > 0)
			{
				foreach ($query->result() as $row)
				{
					$data['username'] = $row->username;
					$data['country_id'] = $row->country_id;
					$data['password'] = $row->password;
					$data['email'] = $row->email;
				}
				
				// Let's insert the new data.
				// Inserts the new user data in the user table.
				$this->user_mdl->insert_user($data);
				
				// If we want to user the custom user profile as well.
				if (Config::item('authoreyes.ae_create_user_profile'))
				{
					// Let's get the last insert id.
					$data_profile['id'] = $query->insert_id();
					$this->user_profile->insert_user_profile($data_profile);
				}
				
				// Activation successful!
				return TRUE;
			}
		}
		
		// Activation failed!
		return FALSE;
	}
	
	/**
	 * Handles the user forgotten password $_POST requests.
	 * 
	 * Returns TRUE if the password is sent to the user, FALSE otherwise.
	 * 
	 * @return bool
	 */
	public function forgotten_password()
	{
		// Checks to see if there is a session.
		if ($this->session)
		{
			$email = $this->input->post('email');
			
			// Ensures the user entered an e-mail address.
			if ($email != FALSE)
			{
				// Fetch the password!
				$query = $this->user_mdl->get_user_forgotten_password($email);
				
				// Makes sure we returned a row.
				if ($query != NULL AND $query->count() > 0)
				{
					// Checks to ensure that the query returned a result.
					$row = $query->result();
					$user_id = $row->id;
					$user = $row->username;
					
					// Generate the activation code.
					$activation_code = $this->_generate_random_string(50, 50);
					
					// Update the user table.
					$this->user_mdl->update_user_forgotten_password($user_id, $activation_code);
					
					// Send an e-mail to the user.
					$this->_send_forgotten_password_email($user_id, $user, $email, $activation_code);
					
					return TRUE;
				}
			}
			// Set unsuccessful FLASH MESSAGE.
			$msg = Kohana::lang('authoreyes.ae_forgotten_password.forgotten_password_user_not_found_message');
			authoreyes_help::flash_message($msg);
			// FIXME : if false is returned, no redirection is done in Front.php
			return FALSE;
		}
	}

	/**
	 * Handles the user forgotten password reset requests when the user clicks on the
	 * e-mail link.
	 * 
	 * Returns TRUE if the process has been successful, FALSE otherwise.
	 * 
	 * @param  int     User ID ($id).
	 * @param  varchar Activation Code ($activation_code).
	 * @return bool
	 */	
	public function forgotten_password_reset($id, $activation_code)
	{
		// Checks to see if ID is valid and activation code isn't blank.
		if ($id > 0 AND $activation_code != '')
		{
			// Fetch the user ID and activation code from the database.
			$query = $this->user_mdl->get_user_forgotten_password_reset($id, $activation_code);
			
			// Checks to ensure that the query returned a result.
			if ($query->count() > 0)
			{
				// Assign our results.
				$row = $query->result();
				$user_id = $row->id;
				$user = $row->username;
				$email = $row->email;
				
				// Generates a random password.
				$password = $this->_generate_random_string(Config::item('authoreyes.ae_user_pass_settings.user_password_min'), Config::item('authoreyes.ae_user_pass_settings.user_password_max'));
				
				// Encrypts the random password using the encryption of your choosing.
				$encrypted_password = $this->hash_password($password);
				
				// Sends the new generated password to the user.
				$this->_send_forgotten_password_reset_email($user_id, $user, $email, $password);
				
				// Updates the password in the database.
				$this->user_mdl->update_user_for_forgotten_password_reset($user_id, $encrypted_password);
				
				return TRUE;
			}
		}
		return FALSE;
	}
	
	/**
	 * Handles the user change password $_POST requests.
	 * 
	 * Returns TRUE if the password is sent to the user, FALSE otherwise.
	 * 
	 * @return bool
	 */
	protected function _change_password()
	{
		// Checks to see if there is a session.
		if ($this->session)
		{
			// Fetch our $_POST data.
			$username = $this->input->post('username');
			$old_password = $this->input->post('old_password');
			$new_password = $this->input->post('new_password');
			
			// Checks to ensure that none of the input is blank.
			if ($username != FALSE AND $old_password != FALSE AND $new_password != FALSE)
			{
				// Fetch the information from the database.
				$query = $this->user_mdl->get_user_for_login($username, $this->hash_password($old_password));
				
				// Make sure the query was successful.
				if ($query != NULL AND $query->count() == 1)
				{
					// Assign our results.
					$row = $query->result();
					$user_id = $row->user_id;
					$user = $row->username;
					$email = $row->email;
					
					// Plain text password for use in the e-mail.
					$password_email = $new_password;
					
					// Encrypt the password for the database update.
					$new_password = $this->hash_password($password);
					
					// Update the user table.
					$this->user_mdl->update_user_forgotten_password_reset($user_id, $new_password);
					
					// Send an e-mail to the user containing all the information required.
					$this->_send_change_password_email($user_id, $user, $email, $password_email);
					
					return TRUE;
				}
			}
			// Set unsuccess FLASH MESSAGE.
			$msg = Kohana::lang('authoreyes.ae_change_password.change_password_failed_message');
			authoreyes_help::flash_message($msg);
			
			// Redirect back to the change password page.
			url::redirect(Config::item('authoreyes.ae_controller_conventions.change_password_uri'), 301);
		}
	}
	
	/**
	 * Sets the userdata in the Db_session table
	 * and updates the user table for Last_login
	 *
	 * @param array $userdata
	 * @return void
	 */	
	protected function _set_login_data($user_data)
	{
		// Updates the last_visit field in the user table.
		$this->user_mdl->update_user_for_login($user_data['id']);
		// Create session.
		$this->session->set($user_data);
	}
	
	/**
	 * Unsets user data in a session.
	 * 
	 * @param  int   Username ($username).
	 * @return void
	 */
	protected function _unset_user($username = NULL)
	{
		// Fetch the username from the session data.
		$user = $this->session->get($username);
		
		if (isset($user))
		{
			// Unset our variable.
			unset($user);
			
			// Deletes the information stored in the session cookie 
			// (or DB, or however you're working with session data).
			$this->session->del(array('id', 'username', 'role', 'remember'));
		}
	}
	
	/**
	 * Needed to clean the user_temp table from incomplete registrations.
	 * 
	 * The records get removed if they are older than what you set in the configuration
	 * file $config['ae_temporary_users_expiration'].
	 * 
	 * Cleaning is performed after activation and on new registrations.
	 * 
	 * !!!!!!!!!!!!!!!!!!CHECK TO SEE IF WE NEED AN ID???!!!!!!!!!!!!!!!!!!!!!
	 * 
	 * @return void
	 */
	public function clean_expired_user_temp()
	{
		// Set our expiration time.
		$expiration = Config::item('authoreyes.ae_temporary_users_expiration');
		
		// Fetch row for temporary registration.
		$query = $this->user_temp->get_user_temp_created();
		
		// If the result exists.
		if ($query->result() > 0)
		{
			foreach ($query->result as $row)
			{
				if (time() > $row->created + $expiration)
				{
					// Delete the row.
					$this->user_temp->delete_user_after_activation($row->id);
				}
			}
		}
	}
	
	/**
	 * Uses session library.
	 * 
	 * Returns the currently logged in user's name.
	 * Returns an empty string if no user is logged in.
	 * 
	 * @return  string
	 */
	public function get_username()
	{
		// Ensure that AuthorEyes is turned on.
		if ($this->session AND Config::item('authoreyes.ae'))
		{
			// Returns string containing username of currently logged in user.
			return $this->session->get('username');
		}
		// Returns empty string if user is not logged in.
		return '';
	}
	
	/**
	 * Returns the currently logged in user's property from the session.
	 * 
	 * A property is what the user gave when registering (like 'email'), or something
	 * calculated server-side (like 'last_visit').
	 * 
	 * Returns an empty string if no user is logged in.
	 * 
	 * @param  string  This can be 'id', 'username', 'country_id', 'email', 'role', 'last_visit', 'created', modified' ($prop).
	 * @return string
	 */
	public function get_user_property($prop)
	{
		// Check to ensure session exists and AuthorEyes is turned on.
		if ($this->session AND Config::item('authoreyes.ae'))
		{
			// Returns the property string of the currently logged in user.
			return $this->session->get($prop);
		}
		// Returns an empty string if the user is not logged in.
		return '';
	}
	
	/**
	 * Returns the property $prop of the user identified by $id from the database.
	 * 
	 * A property is what the user gave when registering (like 'email'), or something
	 * calculated server-side (like 'last_visit').
	 * 
	 * @param  int     The ID of the user you are interested in ($id).
	 * @param  string  This can be 'id', 'username', 'country_id', 'email', 'role', 'last_visit', 'created', modified' ($prop).
	 * @return string
	 */
	public function get_user_property_from_id($id, $prop)
	{
		// Retrieves the row from the database based on the user ID.
		$query = $this->user_mdl->get_user_by_id($id);
		
		// Checks to ensure that the query returned a row.
		if ($query->count() == 1)
		{
			$row = $query->result();
			
			if (isset($row->$prop))
			{
				return $row->$prop;
			}
			else
			{
				return '';
			}
		}
		else
		{
			return Kohana::lang('authoreyes.ae_website.unknown_user_message');
		}
	}
	
//	!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//  !!!!!!!! USE SWIFTMAILER PERHAPS????  ANSWER: YES! !!!!!!!!!
//	!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	
	/**
	 * Sends an e-mail from the system to a given e-mail address.
	 *
	 * @param  varchar E-mail address ($email).
	 * @param  varchar Subject of the e-mail ($subject).
	 * @param  text    Body of the message ($message).
	 * @return void
	 */
	protected function _send_email($email, $subject, $message)
	{
		
	}
	
	/**
	 * Sends an activation e-mail from the system to the newly registered user.
	 *
	 * @param  int     User ID ($id).
	 * @param  varchar User name ($user).
	 * @param  varchar E-mail address ($email).
	 * @param  varchar Activation code ($activation_code).
	 * @return void
	 */
	protected function _send_activation_email($id, $user, $email, $activation_code)
	{
		
	}
	
	/**
	 * Sends an e-mail from the system to the user that has forgotten their
	 * password.  The e-mail contains the link to make the reset password start.
	 *
	 * @param  int     User ID ($id).
	 * @param  varchar User name ($user).
	 * @param  varchar E-mail address ($email).
	 * @param  varchar Activation code ($activation_code).
	 * @return void
	 */
	protected function _send_forgotten_password_email($id, $user, $email, $activation_code)
	{
		
	}

	/**
	 * Sends an e-mail to the user after resetting the password.
	 * The e-mail contains the new login information.
	 *
	 * @param  int     User ID ($id).
	 * @param  varchar User name ($user).
	 * @param  varchar E-mail address ($email).
	 * @param  varchar User's password ($password).
	 * @return void
	 */
	protected function _send_forgotten_password_reset_email($id, $user, $email, $password)
	{
		
	}
	
	/**
	 * Sends an email from the system to the user that has changed the password.
	 * The e-mail contains the newly generated password.
	 * 
	 * @param  int     User ID ($id).
	 * @param  varchar User name ($user).
	 * @param  varchar E-mail address ($email).
	 * @param  varchar Activation code ($activation_code).
	 * @return void
	 */
	protected function _send_change_password_email($id, $user, $email, $activation_code)
	{
		
	}
	
	/**
	 * Gets the login form input values.
	 * 
	 * @return array
	 */
	public function get_login_form()
	{
		// Fetches the values from the $_POST array.
		$values = array(
			'username' => $this->input->post('username', TRUE),
			'password'  => $this->input->post('password', TRUE),
			'remember'  => $this->input->post('remember', TRUE),
		);
		
		return $values;
	}
	
	/**
	 * Gets the registration form input values.
	 * 
	 * @return array
	 */
	public function get_registration_form()
	{
		// Fetches the values from the $_POST array.
		$values = array(
			'username' => $this->input->post('username', TRUE),
			'password'  => $this->input->post('password', TRUE),
			'email'     => $this->input->post('email', TRUE),
		);
		
		if (Config::item('authoreyes.ae_use_country'))
		{
			$values['country_id'] = $this->input->post('country_id');
		}
	}
	
	/**
	 * Custom encoding method for added security.
	 * 
	 * This method was taken from the original Auth library.
	 * 
	 * @param  string Password you want to encode ($password).
	 * @param  string Random character string ($salt).
	 * @return string
	 */
	public function hash_password($password, $salt = FALSE)
	{
		// Set our salt pattern.
		$salt_pattern = Config::item('authoreyes.ae_user_pass_settings.salt_pattern');
		$this->salt_pattern = preg_split('/, ?/', $salt_pattern);
		
		if ($salt == FALSE)
		{
			// Create a salt seed, same length as the number of offsets in the pattern.
			$salt = substr($this->hash(uniqid(NULL, TRUE)), 0, count($this->salt_pattern));
		}

		// Password hash that the salt will be inserted into.
		$hash = $this->_hash($salt.$password);

		// Change salt to an array.
		$salt = str_split($salt, 1);

		// Returned password.
		$password = '';

		// Used to calculate the length of splits.
		$last_offset = 0;

		foreach($this->salt_pattern as $offset)
		{
			// Split a new part of the hash off.
			$part = substr($hash, 0, $offset - $last_offset);

			// Cut the current part out of the hash.
			$hash = substr($hash, $offset - $last_offset);

			// Add the part to the password, appending the salt character.
			$password .= $part.array_shift($salt);

			// Set the last offset to the current offset.
			$last_offset = $offset;
		}

		// Return the password, with the remaining hash appended.
		return $password.$hash;
	}
	
	/**
	 * Perform a hash, using the configured method.
	 * 
	 * Change the hash method in the config file:
	 * 	- $config['ae_user_pass_settings']['hash_method'] = 'sha1'
	 * 
	 * @param  str String to be hashed ($str).
	 * @return str
	 */
	protected function _hash($str)
	{
		return hash(Config::item('authoreyes.ae_user_pass_settings.hash_method'), $str);
	}
	
	/**
	 * Finds the salt from a password, based on the configured salt pattern.
	 * 
	 * @param  string Hashed password.
	 * @return string
	 */
	protected function _find_salt($password)
	{
		$salt = '';
		
		foreach ($this->salt_pattern as $i => $offset)
		{
			// Find salt characters.
			$salt .= substr($password, $offset + $i, 1);
		}
		
		return $salt;
	}
	
	/**
	 * Method to display and edit user profile data.
	 *
	 * @param  integer User ID ($id).
	 * @return mixed
	 */
	protected function _get_user_profile($id)
	{
		// Retrieve the field names from the config file.
		// !!!!!!!!!!!!! WHY DO WE NEED THIS??? !!!!!!!!!!!!!
		// $field_names = Config::item('authoreyes.ae_user_profile_field_names');
		
		// Retrieve the field validation rules from the config file.
		$field_rules = Config::item('authoreyes.ae_user_profile_fields_validation_rules');
		
		// Retrieve the fields from the database.
		$db_fields = $this->user_profile->get_table_fields();
		
		// Number of database fields - 1.
		// -1 required because we don't count the 'id' field.
		$num_db_fields = count($db_fields) - 1;
		
		// Checks to ensure that there are fields other than 'id'.
		if ($num_db_fields != 0)
		{
			// Retrieves the user profile.
			$query = $this->user_profile->get_user_profile_by_id($id);
			
			if ($query->count() == 1)
			{
				// Assign the results to a local variable.
				$row = $query->result();
				
				// Loop to assign the fields/values to an array.
				for ($i = 1; $i < $num_db_fields; $i++)
				{
					$field = $db_fields[$i];
					$data[$field] = $row->$db_fields[$i];
				}
				
				return $data;
			}
		}
		// If there are no other fields aside from 'id'.
		else
		{
			return FALSE;
		}
	}
	
	/**
	 * Method to dynamically build rules and fields from config array for add
	 * and edit custom user profile.
	 *
	 * @return array
	 */
	protected function _build_user_profile_field_rules()
	{
		// Retrieve the field names from the config file.
		$field_names = Config::item('authoreyes.ae_user_profile_fields_names');
		
		// Retrieve the field validation rules from the config file.
		$field_rules = Config::item('authoreyes.ae_user_profile_fields_validation_rules');
		
		// Retrieve the fields from the database.
		$db_fields = $this->user_profile->get_table_fields();
		
		// Number of database fields - 1.
		// -1 required because we don't count the 'id' field.
		$num_db_fields = count($db_fields) - 1;
		
		// Using 'for' instead of 'foreach' because we have to escape the
		// 'id' field that has key = 0 in my array
		for ($i = 1; $i <= $num_db_fields;  $i++)
		{
			
			$field = $db_fields[$i];
			
			// If the rule for the fields in the database have been specified in the config array,
			// let's assign it, otherwise don't.
			array_key_exists($field, $field_rule) ? $data['rules'][$field] = $field_rule[$field] : '';
			
			// If the custom field name for the field in the database has been specified in the config array,
			// let's assign it, otherwise let's call it with the name in the database.
			array_key_exists($field, $field_name) ? $data['fields'][$field] = $field_name[$field] : $data['fields'][$field] = $field;
		}
		
		return $data;
	}
	
	/**
	 * Determines if a role exists (uses the configuration file).
	 * 
	 * If you are getting this exception:
	 * 	- Check config/authoreyes.php - $config['ae_roles']
	 *  - Ensure that the role exists with the proper auth level (3 - 99)
	 *  
	 *  An example of the what should be passed through ($check_role):
	 *   1.  Single value string - 'editor'.
	 *   2.  Multi-value string, comma separated - 'editor,user,admin'.
	 *   3.  Array - $sample = array('editor', 'user', 'admin')
	 * 
	 * @param  string The role that you want to check ($check_role).
	 * @return void
	 */
	protected function _role_exists($check_role)
	{
		if (!is_array($check_role))
		{
			// Turn $check_role into an array.
			$check_role_array = explode(',', $check_role);
		}
		else
		{
			// $check_role is already an array so just assign
			// it to $check_role_array for the loop
			$check_role_array = $check_role;
		}
		
		// Loop through each value of $check_role_array and do the check.
		foreach ($check_role_array as $chk)
		{
			// Make sure there's no whitespace.
			$chk = trim($chk);
			
			// Make sure that the role/roles the developer passes exist in the system.
			if (!array_key_exists($chk, Config::item('authoreyes.ae_roles')))
			{
				// Throw an exception letting developer know that they're using a role that doesn't exist.
				throw new Kohana_Exception('authoreyes.ae_debug.role_does_not_exist', $chk);				
			}
		}
	}
} //End Authoreyes_Core